import java.lang.*;
import java.io.*;
import java.util.*;
import javax.microedition.io.*;
import javax.bluetooth.*;
import javax.bluetooth.UUID;

/**
 * This class shows a simple client application that performs device and service
 * discovery and communicates with a print server to show how the Java API for
 * Bluetooth wireless technology works.
 */
public class PrintClient implements DiscoveryListener {
	/**
	 * The DiscoveryAgent for the local Bluetooth device.
	 */
	private DiscoveryAgent agent;
	/**
	 * The max number of service searches that can occur at any one time.
	 */
	private int maxServiceSearches = 0;
	/**
	 * The number of service searches that are presently in progress.
	 */
	private int serviceSearchCount;
	/**
	 * Keeps track of the transaction IDs returned from searchServices.
	 */
	private int transactionID[];
	/**
	 * The service record to a printer service that can print the message
	 * provided at the command line.
	 */
	private ServiceRecord record;
	/**
	 * Keeps track of the devices found during an inquiry.
	 */
	private Vector deviceList;

	/**
	 * Creates a PrintClient object and prepares the object for device discovery
	 * and service searching.
	 * 
	 * @exception BluetoothStateException
	 *                if the Bluetooth system could not be initialized
	 */
	public PrintClient() throws BluetoothStateException {
		/*
		 * Retrieve the local Bluetooth device object.
		 */
		LocalDevice local = LocalDevice.getLocalDevice();
		/*
		 * Retrieve the DiscoveryAgent object that allows us to perform device
		 * and service discovery.
		 */
		agent = local.getDiscoveryAgent();
		/*
		 * Retrieve the max number of concurrent service searches that can exist
		 * at any one time.
		 */
		try {
			maxServiceSearches = Integer.parseInt(LocalDevice
					.getProperty("bluetooth.sd.trans.max"));
		} catch (NumberFormatException e) {
			System.out.println("General Application Error");
			System.out.println("\tNumberFormatException: " + e.getMessage());
		}
		transactionID = new int[maxServiceSearches];
		// Initialize the transaction list
		for (int i = 0; i < maxServiceSearches; i++) {
			transactionID[i] = -1;
		}
		record = null;
		deviceList = new Vector();
	}

	/**
	 * Adds the transaction table with the transaction ID provided.
	 * 
	 * @param trans
	 *            the transaction ID to add to the table
	 */
	private void addToTransactionTable(int trans) {
		for (int i = 0; i < transactionID.length; i++) {
			if (transactionID[i] == -1) {
				transactionID[i] = trans;
				return;
			}
		}
	}

	/**
	 * Removes the transaction from the transaction ID table.
	 * 
	 * @param trans
	 *            the transaction ID to delete from the table
	 */
	private void removeFromTransactionTable(int trans) {
		for (int i = 0; i < transactionID.length; i++) {
			if (transactionID[i] == trans) {
				transactionID[i] = -1;
				return;
			}
		}
	}

	/**
	 * Completes a service search on each remote device in the list until all
	 * devices are searched or until a printer is found that this application
	 * can print to.
	 * 
	 * @param devList
	 *            the list of remote Bluetooth devices to search
	 * 
	 * 
	 * @return true if a printer service is found; otherwise false if no printer
	 *         service was found on the devList provided
	 */
	private boolean searchServices(RemoteDevice[] devList) {
		UUID[] searchList = new UUID[2];
		/*
		 * Add the UUID for L2CAP to make sure that the service record found
		 * will support L2CAP. This value is defined in the Bluetooth Assigned
		 * Numbers document.
		 */
		searchList[0] = new UUID(0x0100);
		/*
		 * Add the UUID for the printer service that we are going to use to the
		 * list of UUIDs to search for. (a fictional printer service UUID)
		 */
		searchList[1] = new UUID("1020304050d0708093a1b121d1e1f100", false);

		searchList = new UUID[] { new UUID(0x1105) };

		/*
		 * Start a search on as many devices as the system can support.
		 */
		for (int i = 0; i < devList.length; i++) {
			/*
			 * If we found a service record for the printer service, then we can
			 * end the search.
			 */
			if (record != null) {
				return true;
			}
			try {
				System.out.println("searchServices");
				int trans = agent.searchServices(null, searchList, devList[i],
						this);
				addToTransactionTable(trans);
			} catch (BluetoothStateException e) {
				/*
				 * Failed to start the search on this device, try another
				 * device.
				 */
			}
			/*
			 * Determine if another search can be started. If not, wait for a
			 * service search to end.
			 */
			synchronized (this) {
				serviceSearchCount++;
				if (serviceSearchCount == maxServiceSearches) {
					try {
						this.wait();
					} catch (Exception e) {
					}
				}
			}
		}
		/*
		 * Wait until all the service searches have completed.
		 */
		while (serviceSearchCount > 0) {
			synchronized (this) {
				try {
					this.wait();
				} catch (Exception e) {
				}
			}
		}
		if (record != null) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Finds the first printer that is available to print to.
	 * 
	 * @return the service record of the printer that was found; null if no
	 *         printer service was found
	 */
	public ServiceRecord findPrinter() {
		/*
		 * If there are any devices that have been found by a recent inquiry, we
		 * don't need to spend the time to complete an inquiry.
		 */
		RemoteDevice[] devList = agent.retrieveDevices(DiscoveryAgent.CACHED);
		if (devList != null) {
			if (searchServices(devList)) {
				return record;
			}
		}
		/*
		 * Did not find any printer services from the list of cached devices.
		 * Will try to find a printer service in the list of pre-known devices.
		 */
		devList = agent.retrieveDevices(DiscoveryAgent.PREKNOWN);
		if (devList != null) {
			if (searchServices(devList)) {
				return record;
			}
		}
		/*
		 * Did not find a printer service in the list of pre-known or cached
		 * devices. So start an inquiry to find all devices that could be a
		 * printer and do a search on those devices.
		 */
		/* Start an inquiry to find a printer */
		try {
			System.out.println("startInquiry");
			agent.startInquiry(DiscoveryAgent.GIAC, this);
			/*
			 * Wait until all the devices are found before trying to start the
			 * service search.
			 */
			synchronized (this) {
				try {
					this.wait();
				} catch (Exception e) {
				}
			}
		} catch (BluetoothStateException e) {
			System.out.println("Unable to find devices to search");
		}
		if (deviceList.size() > 0) {

			devList = new RemoteDevice[deviceList.size()];
			deviceList.copyInto(devList);
			if (searchServices(devList)) {
				return record;
			}
		}
		return null;
	}

	/**
	 * This is the main method of this application. It will print out the
	 * message provided to the first printer that it finds.
	 * 
	 * @param args
	 *            [0] the message to send to the printer
	 */
	public static void main(String[] args) {
		PrintClient client = null;
		/*
		 * Validate the proper number of arguments exist when starting this
		 * application.
		 */
		// if ((args == null) || (args.length != 1)) {
		// System.out.println("usage: java PrintClient message");
		// return;
		// }
		/*
		 * Create a new PrintClient object.
		 */
		try {
			client = new PrintClient();
		} catch (BluetoothStateException e) {
			System.out.println("Failed to start Bluetooth System");
			System.out.println("\tBluetoothStateException: " + e.getMessage());
		}
		/*
		 * Find a printer in the local area
		 */
		ServiceRecord printerService = client.findPrinter();
		if (printerService != null) {

			/*
			 * Determine if this service will communicate over RFCOMM or L2CAP
			 * by retrieving the connection string.
			 */
			String conURL = printerService.getConnectionURL(
					ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
			int index = conURL.indexOf(':');
			String protocol = conURL.substring(0, index);
			if (protocol.equals("btspp")) {
				/*
				 * Since this printer service uses RFCOMM, create an RFCOMM
				 * connection and send the data over RFCOMM.
				 */
				/* code to call RFCOMM client goes here */
			} else if (protocol.equals("btl2cap")) {
				/*
				 * Since this service uses L2CAP, create an L2CAP connection to
				 * the service and send the data to the service over L2CAP.
				 */
				/* code to call L2CAP client goes here */
			} else {
				System.out.println("Unsupported Protocol");
			}
		} else {
			System.out.println("No Printer was found");
		}
	}

	/**
	 * Called when a device was found during an inquiry. An inquiry searches for
	 * devices that are discoverable. The same device may be returned multiple
	 * times.
	 * 
	 * @see DiscoveryAgent#startInquiry
	 * 
	 * @param btDevice
	 *            the device that was found during the inquiry
	 * 
	 * 
	 * 
	 * @param cod
	 *            the service classes, major device class, and minor device
	 *            class of the remote device being returned
	 * 
	 */
	public void deviceDiscovered(RemoteDevice btDevice, DeviceClass cod) {
		System.out.println("deviceDiscovered.");

		deviceList.addElement(btDevice);
		// /*
		// * Since service search takes time and we are already forced to
		// complete
		// * an inquiry, we will not do a service search on any device that is
		// not
		// * an Imaging device. The device class of 0x600 is Imaging as defined
		// in
		// * the Bluetooth Assigned Numbers document.
		// */
		// if (cod.getMajorDeviceClass() == 0x600) {
		// /*
		// * Imaging devices could be a display, camera, scanner, or printer.
		// * If the imaging device is a printer, then bit 7 should be set from
		// * its minor device class according to the Bluetooth Assigned
		// * Numbers document.
		// */
		// if ((cod.getMinorDeviceClass() & 0x80) != 0) {
		// /*
		// * Now we know that it is a printer. Now we will verify that it
		// * has a rendering service on it. A rendering service may allow
		// * us to print. We will have to do a service search to get more
		// * information if a rendering service exists. If this device has
		// * a rendering service then bit 18 will be set in the major
		// * service classes.
		// */
		// if ((cod.getServiceClasses() & 0x40000) != 0) {
		// deviceList.addElement(btDevice);
		// }
		// }
		// }
	}

	/**
	 * The following method is called when a service search is completed or was
	 * terminated because of an error. Legal status values include:
	 * <code>SERVICE_SEARCH_COMPLETED</code>,
	 * <code>SERVICE_SEARCH_TERMINATED</code>, <code>SERVICE_SEARCH_ERROR</code>
	 * ,
	 * 
	 * 
	 * <code>SERVICE_SEARCH_DEVICE_NOT_REACHABLE</code>, and
	 * <code>SERVICE_SEARCH_NO_RECORDS</code>.
	 * 
	 * @param transID
	 *            the transaction ID identifying the request which initiated the
	 *            service search
	 * 
	 * @param respCode
	 *            the response code which indicates the status of the
	 *            transaction; guaranteed to be one of the aforementioned only
	 * 
	 */
	public void serviceSearchCompleted(int transID, int respCode) {
		System.out.println("serviceSearchCompleted.");

		/*
		 * Removes the transaction ID from the transaction table.
		 */
		removeFromTransactionTable(transID);
		serviceSearchCount--;
		synchronized (this) {
			this.notifyAll();
		}
	}

	/**
	 * Called when service(s) are found during a service search. This method
	 * provides the array of services that have been found.
	 * 
	 * @param transID
	 *            the transaction ID of the service search that is posting the
	 *            result
	 * 
	 * @param service
	 *            a list of services found during the search request
	 * 
	 * @see DiscoveryAgent#searchServices
	 */
	public void servicesDiscovered(int transID, ServiceRecord[] servRecord) {
		/*
		 * If this is the first record found, then store this record and cancel
		 * the remaining searches.
		 */

		System.out.println(servRecord.length + " servicesDiscovered.");

		if (record == null) {
			record = servRecord[0];
			
			/*
			 * Cancel all the service searches that are presently being
			 * performed.
			 */
			for (int i = 0; i < transactionID.length; i++) {
				if (transactionID[i] != -1) {
					agent.cancelServiceSearch(transactionID[i]);
				}
			}
		}
	}

	/**
	 * Called when a device discovery transaction is completed. The
	 * <code>discType</code> will be <code>INQUIRY_COMPLETED</code> if the
	 * device discovery transactions ended normally, <code>INQUIRY_ERROR</code>
	 * if the device discovery transaction failed to complete normally,
	 * <code>INQUIRY_TERMINATED</code> if the device discovery transaction was
	 * canceled by calling <code>DiscoveryAgent.cancelInquiry()</code>.
	 * 
	 * @param discType
	 *            the type of request that was completed; one of
	 *            <code>INQUIRY_COMPLETED</code>, <code>INQUIRY_ERROR</code> or
	 *            <code>INQUIRY_TERMINATED</code>
	 */
	public void inquiryCompleted(int discType) {
		System.out.println("inquiryCompleted.");
		synchronized (this) {
			try {
				this.notifyAll();
			} catch (Exception e) {
			}
		}
	}
}
